package com.sh.data.engine.velocity.tool;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import com.sh.data.engine.velocity.model.iotdb.IotDataModel;
import com.sh.data.engine.velocity.model.iotdb.IotDataModel.MeasurementModel;
import net.minidev.json.JSONObject;
import org.apache.velocity.tools.Scope;
import org.apache.velocity.tools.config.DefaultKey;
import org.apache.velocity.tools.config.InvalidScope;
import org.apache.velocity.tools.generic.SafeConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

/**
 * iotdb时序 解析通常分为4步，后续如果存在无法解析的消息体格式，可以分别针对各个步骤进行扩展 1、设置存储组 2、设置设备id 3、设置时间戳 4、解析测点值 示例脚本如下：
 * jsonpath 和 velocity 的$符号可能会引起冲突，jsonpath的$使用&代替
 * $iot.setStorageGroupDirect($IOT_DATA_MODEL,"root.ninggang")
 * $iot.setDeviceIdFromJson($IOT_DATA_MODEL,"&.sn")
 * $iot.setTimestampFromJson($IOT_DATA_MODEL,"&.time")
 * #set($result=$iot.setMeasurementFromJson($IOT_DATA_MODEL,"&.data.YS_YS.*","id","value"))
 */
@DefaultKey("iot")
@InvalidScope({Scope.APPLICATION, Scope.SESSION})
public class IotTool extends SafeConfig implements Serializable {

    private static final Logger logger = LoggerFactory.getLogger(IotTool.class);

    /**
     * 第一步 设置存储组，直接塞值
     *
     * @param dataModel
     * @param storageGroup
     */
    public static void setStorageGroupDirect(IotDataModel dataModel, String storageGroup) {
        if (StrUtil.isBlank(storageGroup)) {
            throw new RuntimeException("存储组名称不能为空");
        }
        dataModel.setStorageGroup(storageGroup);
        logger.info("存储组名称设置成功：{}", storageGroup);
    }

    /**
     * 第一步 设置存储组，解析消息报文
     *
     * @param dataModel
     * @param parsePattern
     */
    public static void setStorageGroupFromJson(IotDataModel dataModel, String parsePattern) {
        String storageGroup =
            IotTool.parseBaseInfo(JsonPathTool.parse(dataModel.getJson(), parsePattern));
        dataModel.setStorageGroup(storageGroup);
        logger.info("存储组名称设置成功：{}", storageGroup);
    }

    /**
     * 第二步 设置设备名称
     *
     * @param dataModel
     * @param deviceId
     */
    public static void setDeviceIdDirect(IotDataModel dataModel, String deviceId) {
        if (StrUtil.isBlank(deviceId)) {
            throw new RuntimeException("设备名称不能为空");
        }
        dataModel.setDeviceId(deviceId);
        logger.info("设备名称设置成功：{}", deviceId);
    }

    /**
     * 第二步 设置设备名称，解析报文后得到
     *
     * @param dataModel
     * @param parsePattern
     */
    public static void setDeviceIdFromJson(IotDataModel dataModel, String parsePattern) {
        String deviceId = IotTool.parseBaseInfo(JsonPathTool.parse(dataModel.getJson(), parsePattern));
        dataModel.setDeviceId(deviceId);
        logger.info("设备名称设置成功：{}", deviceId);
    }

    /**
     * 第三步 设置时间戳，直接解析
     *
     * @param dataModel
     * @param parsePattern
     */
    public static void setTimestampFromJson(IotDataModel dataModel, String parsePattern) {
        String timeStr = IotTool.parseBaseInfo(JsonPathTool.parse(dataModel.getJson(), parsePattern));
        long time = DateUtil.parse(timeStr).getTime();
        dataModel.setTimestamp(time);
        logger.info("时间戳设置成功：{}", time);
    }

    /**
     * 第三步 设置时间戳，根据指定日期格式解析
     *
     * @param dataModel
     * @param parsePattern 日期解析格式
     * @param timeFormat
     */
    public static void setTimestampFromJson(
        IotDataModel dataModel, String parsePattern, String timeFormat) {
        String timeStr = IotTool.parseBaseInfo(JsonPathTool.parse(dataModel.getJson(), parsePattern));
        long time = DateUtil.parse(timeStr, timeFormat).getTime();
        dataModel.setTimestamp(time);
        logger.info("时间戳设置成功：{}", time);
    }

    public static void setFilterTagAndValue(
        IotDataModel dataModel, String parsePattern, String values) {
        if (dataModel.isInvalid()) {
            return;
        }
        String parseValue =
            IotTool.parseBaseInfo(JsonPathTool.parse(dataModel.getJson(), parsePattern));
        if (StrUtil.isNotBlank(values)) {
            List<String> valList = Arrays.asList(values.split(","));
            boolean flag = !valList.contains(parseValue);
            dataModel.setInvalid(flag);
            logger.info("按标签值过滤成功，期望值：{}，实际值：{}，是否非法：{}", values, parseValue, flag);
        }
    }

    /**
     * 第四步 设置测点值
     *
     * @param dataModel
     * @param parsePattern
     * @param nameKey
     * @param valueKey
     * @return
     */
    public static List<IotDataModel> setMeasurementFromJson(
        IotDataModel dataModel, String parsePattern, String nameKey, String valueKey) {
        // logger.info("parsePattern:{}", parsePattern);
        List<MeasurementModel> measurements =
            IotTool.parseMeasurement(
                JsonPathTool.parse(dataModel.getJson(), parsePattern), nameKey, valueKey, null, null);
        measurements =
            measurements.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());
        dataModel.setMeasurements(measurements);
        logger.info("测点值解析成功，有效测点数量：{}", measurements.size());
        return Arrays.asList(dataModel);
    }

    /**
     * 同时解析时间戳+测点值 { "deviceNo" : "16089617", "fsDeviceValue" : "2.6", "fxDeviceValue" : "31",
     * "createTime" : "2023-02-21 18:17:37" }
     *
     * @param dataModel
     * @param parsePattern
     * @param nameKey
     * @param valueKey
     * @param tagKey
     * @param validValue
     * @return
     */
    public static List<IotDataModel> setMeasurementWithTagAndValueFromJson(
        IotDataModel dataModel,
        String parsePattern,
        String nameKey,
        String valueKey,
        String tagKey,
        String validValue) {
        // logger.info("parsePattern:{}", parsePattern);
        List<MeasurementModel> measurements =
            IotTool.parseMeasurement(
                JsonPathTool.parse(dataModel.getJson(), parsePattern),
                nameKey,
                valueKey,
                tagKey,
                validValue);
        measurements =
            measurements.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());
        dataModel.setMeasurements(measurements);
        logger.info("测点值解析成功，有效值标签：{}，有效值：{}，有效测点数量：{}", tagKey, validValue, measurements.size());
        return Arrays.asList(dataModel);
    }

    // 同时设置时间戳和测点值
    public static List<IotDataModel> setTimestampAndMeasurementFromJson(
        IotDataModel dataModelV1,
        String parsePattern,
        String timestampKey,
        String measurementKeys,
        String measurementPrefixKey) {

        List<IotDataModel> dataModelsV1 =
            IotTool.parseTimestampAndMeasurements(
                JsonPathTool.parse(dataModelV1.getJson(), parsePattern),
                timestampKey,
                measurementKeys,
                measurementPrefixKey);
        List<IotDataModel> dataModelsV2 = Lists.newArrayList();

        Map<Long, List<IotDataModel>> group =
            dataModelsV1.stream().collect(Collectors.groupingBy(IotDataModel::getTimestamp));
        group.forEach(
            (time, gp) -> {
                IotDataModel dataModelV2 = new IotDataModel();
                dataModelV2.setStorageGroup(dataModelV1.getStorageGroup());
                dataModelV2.setDeviceId(dataModelV1.getDeviceId());
                dataModelV2.setTimestamp(time);
                List<MeasurementModel> measurements = Lists.newArrayList();
                gp.stream()
                    .forEach(
                        g -> {
                            measurements.addAll(g.getMeasurements());
                        });
                List<MeasurementModel> measurementsV2 =
                    measurements.stream().distinct().collect(Collectors.toList());
                dataModelV2.setMeasurements(measurementsV2);
                dataModelsV2.add(dataModelV2);
            });

        return dataModelsV2;
    }

    /**
     * 解析除测点数据之外的基本数据
     *
     * @param jsonObjOrArray
     * @return
     */
    private static String parseBaseInfo(Object jsonObjOrArray) {
        String baseInfo = "";
        if (jsonObjOrArray == null) {
            return null;
        }
        if (jsonObjOrArray instanceof net.minidev.json.JSONArray) {
            net.minidev.json.JSONArray jsonArray = (net.minidev.json.JSONArray) jsonObjOrArray;
            baseInfo = (String) jsonArray.get(0);
            return baseInfo;
        }
        if (jsonObjOrArray instanceof Map) {
            throw new RuntimeException("not support yet");
        }
        if (jsonObjOrArray instanceof JSONObject) {
            throw new RuntimeException("not support yet");
        }
        return baseInfo;
    }

    /**
     * 解析时间戳+测点值
     *
     * @param jsonObjOrArray
     * @param timestampKey
     * @param measurementKeys
     * @param measurementPrefixKey
     * @return
     */
    private static List<IotDataModel> parseTimestampAndMeasurements(
        Object jsonObjOrArray,
        String timestampKey,
        String measurementKeys,
        String measurementPrefixKey) {
        if (jsonObjOrArray == null) {
            return null;
        }
        List<IotDataModel> dataModels = Lists.newArrayList();

        if (jsonObjOrArray instanceof net.minidev.json.JSONArray) {
            net.minidev.json.JSONArray jsonArray = (net.minidev.json.JSONArray) jsonObjOrArray;
            jsonArray.stream()
                .forEach(
                    e -> {
                        if (e instanceof net.minidev.json.JSONArray) {
                            net.minidev.json.JSONArray f = (net.minidev.json.JSONArray) e;
                            f.stream()
                                .forEach(
                                    g -> {
                                        IotDataModel dm = new IotDataModel();
                                        List<MeasurementModel> measurements = new ArrayList<>();
                                        Map<String, Object> map = (Map<String, Object>) g;
                                        // 解析时间戳
                                        if (!Objects.isNull(map.get(timestampKey))) {
                                            String timeStr = (String) (map.get(timestampKey));
                                            long time = DateUtil.parse(timeStr).getTime();
                                            dm.setTimestamp(time);
                                        }
                                        // 解析前缀
                                        String prefixVal = "";
                                        if (!Objects.isNull(map.get(measurementPrefixKey))) {
                                            prefixVal = (String) (map.get(measurementPrefixKey));
                                        }
                                        // 解析测点值
                                        List<String> mKeys = Arrays.asList(measurementKeys.split(","));
                                        for (String mkey : mKeys) {
                                            if (!Objects.isNull(map.get(mkey))) {
                                                Object value = map.get(mkey);
                                                MeasurementModel measurementModel = new MeasurementModel();
                                                String name = mkey;
                                                if (StrUtil.isNotBlank(prefixVal)) {
                                                    // name = String.join("_", prefix, name);
                                                    name = String.join(".", prefixVal, name);
                                                }
                                                name = "`" + name + "`";
                                                measurementModel.setName(name);
                                                measurementModel.setValue(value);

                                                measurements.add(measurementModel);
                                            }
                                        }
                                        dm.setMeasurements(measurements);
                                        dataModels.add(dm);
                                    });
                        }
                    });
        }
        return dataModels;
    }

    /**
     * 解析测点值
     *
     * @param jsonObjOrArray
     * @param nameKey
     * @param valueKey
     * @return
     */
    private static List<MeasurementModel> parseMeasurement(
        Object jsonObjOrArray, String nameKey, String valueKey, String tagKey, String validVal) {
        if (jsonObjOrArray == null) {
            return null;
        }
        List<MeasurementModel> measurements = new ArrayList<>();
        if (jsonObjOrArray instanceof net.minidev.json.JSONArray) {
            net.minidev.json.JSONArray jsonArray = (net.minidev.json.JSONArray) jsonObjOrArray;
            jsonArray.stream()
                .forEach(
                    e -> {
                        if (e instanceof net.minidev.json.JSONArray) {
                            net.minidev.json.JSONArray f = (net.minidev.json.JSONArray) e;
                            f.stream()
                                .forEach(
                                    g -> {
                                        measurements.add(
                                            IotTool.getSingleMeasurement(
                                                g, "", nameKey, valueKey, tagKey, validVal));
                                    });
                        } else if (e instanceof Map) {
                            // 带有分类的测点标签，把分类值也带上 类似于"C1_D1":{...}
                            Map<String, Object> measurementsMap = (Map) e;
                            measurementsMap.forEach(
                                (mkey, mList) -> {
                                    if (mList instanceof net.minidev.json.JSONArray) {
                                        net.minidev.json.JSONArray f = (net.minidev.json.JSONArray) mList;
                                        f.stream()
                                            .forEach(
                                                g -> {
                                                    measurements.add(
                                                        IotTool.getSingleMeasurement(
                                                            g, mkey, nameKey, valueKey, tagKey, validVal));
                                                });
                                    }
                                });
                        } else {
                            measurements.add(
                                IotTool.getSingleMeasurement(e, "", nameKey, valueKey, tagKey, validVal));
                        }
                    });
            return measurements;
        }
        if (jsonObjOrArray instanceof Map) {
            measurements.add(
                IotTool.getSingleMeasurement(jsonObjOrArray, "", nameKey, valueKey, tagKey, validVal));
        }
        if (jsonObjOrArray instanceof JSONObject) {
            // 带有分类的测点标签，把分类值也带上
            JSONObject jo = (JSONObject) jsonObjOrArray;
            jo.forEach(
                (key, val) -> {
                    if (val instanceof net.minidev.json.JSONArray) {
                        net.minidev.json.JSONArray f = (net.minidev.json.JSONArray) val;
                        f.stream()
                            .forEach(
                                g -> {
                                    measurements.add(
                                        IotTool.getSingleMeasurement(
                                            g, key, nameKey, valueKey, tagKey, validVal));
                                });
                    }
                });
            // throw new RuntimeException("not support yet");
        }
        return measurements;
    }

    /**
     * 获取单个测点
     *
     * @param e
     * @param nameKey
     * @param valueKey
     * @return
     */
    private static MeasurementModel getSingleMeasurement(
        Object e, String prefix, String nameKey, String valueKey, String tagKey, String validValue) {
        Map<String, Object> map = (Map<String, Object>) e;
        // 校验测点值是否合法
        if (StrUtil.isNotBlank(tagKey) && map.containsKey(tagKey)) {
            String realTagValue = (String) map.get(tagKey);
            if (StrUtil.isNotBlank(validValue)) {
                List<String> validTagValues = Arrays.asList(validValue.split(","));
                if (!validTagValues.contains(realTagValue)) {
                    return null;
                }
            }
        }
        // 解析测点值
        if (!Objects.isNull(map.get(nameKey))) {
            String name = (String) map.get(nameKey);
            Object value = map.get(valueKey);
            // name = name.replaceAll("\\.", "_");
            MeasurementModel measurementModel = new MeasurementModel();
            if (StrUtil.isNotBlank(prefix)) {
                // name = String.join("_", prefix, name);
                name = String.join(".", prefix, name);
            }
            name = "`" + name + "`";
            measurementModel.setName(name);
            measurementModel.setValue(value);
            return measurementModel;
        }
        return null;
    }
}
